<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <style>
    .point {
      color: red;
      font-weight: 500;
    }
  </style>
</head>
<body style="font-size: 20px">
<pre>
    <span class="point">函数的 this 关键字</span>
    在绝大多数情况下，函数的调用方式决定了this的值。(bind/()=>{}除外)
    this->当前执行代码的环境对象
    ->函数=网吧电脑，this=正在用电脑的那个兄弟
    将一个对象作为call和apply的第一个参数，this会被绑定到这个对象
  <span class="point">使用函数继承自Function.prototype 的 call 或 apply 方法将 this 值绑定到调用中的特定对象。</span>
  f.bind(someObject)会创建一个与f具有<span class="point">相同函数体和作用域的函数</span>
  <span class="point">bind/箭头函数会固定this</span>
  但是在这个新函数中，this将永久地被绑定到了bind的第一个参数，无论这个函数是如何被调用的。
  箭头函数的this被设置为封闭的词法环境的。
  箭头函数（函数体内的this对象，就是<span class="point">定义</span>时所在的对象，而不是使用时所在的对象。
  this指向的是调用这个方法的对象，就像该方法在对象上一样
  虽然构造器返回的默认值是this所指的那个对象，但它仍可以手动返回其他的对象（如果返回值不是一个对象，则返回this对象）->return
  当函数被用作事件处理函数时，它的this指向触发事件的元素（一些浏览器在使用非addEventListener的函数动态添加监听函数时不遵守这个约定）。
  <span class="point">this对象的指向是可变的，但是在箭头函数中，它是固定的。</span>
  箭头函数可以让this指向固定化，这种特性很有利于封装回调函数
  并不是因为箭头函数内部有绑定this的机制，实际原因是箭头函数根本没有自己的this，导致内部的this就是外层代码块的this
  箭头函数没有自己的this，所以当然也就不能用call()、apply()、bind()这些方法去改变this的指向。

  不适用场合
  1.定义对象的方法，且该方法内部包括this
  2.需要动态this的时候，也不应使用箭头函数
  </pre>
<button id="btn">click</button>
<button onclick="alert(this.tagName.toLowerCase());">
  Show this
</button>
<button onclick="alert((function(){return this})());">
  Show inner this 没有指明的this对象的时候都是指向window
</button>
<script>
  console.group('start')
  console.log(this === window)
  var a = 100

  function m1() {
    return this.a
  }

  function m2(b, c) {
    return this.a + b + c
  }

  let obj = {
    a: 10,
    m1: m1
  }

  console.log(window.m1())
  console.log(obj.m1())

  console.groupEnd()

  console.group('apply 参数设置为数组')
  let obj1 = {
    a: 'obj1'
  }
  console.log(m1.apply(obj1))
  console.log(m2.apply(obj1, [' test', ' this']))
  console.groupEnd()
  console.group('call ')
  let obj2 = {
    a: 'obj2'
  }

  console.log(m1.call(obj2))
  console.log(m2.call(obj2, ' test', ' this'))
  console.groupEnd()
  console.group('bind')
  obj3 = {
    a: 'bind test'
  }
  let obj3m3 = m1.bind(obj3)
  console.log(obj3m3())
  console.log(obj3m3.call(obj1))// bind test
  console.log(obj3m3.apply(obj2))// bind test
  console.groupEnd()
  console.group('箭头函数')
  var foo = () => this;
  console.log(foo() === window); // true
  var bar1 = () => this.a;//this 被设置为他被创建时的环境 window
  let obj4 = {
    a: '箭头函数',
    bar1: bar1
  }
  console.log(obj4.bar1())//100 window
  console.groupEnd()
  console.group('箭头函数2 同bind一样 作用域不再改变')
  let obj5 = {
    a: 'obj5',
    f: () => this.a
  }
  let obj6 = {
    a: 'obj6',
    f: (() => this.a)//全局定义的
  }
  let obj7 = {
    a: 'obj7',
    f: function () {
      return () => this.a;//对象定义的 绑定在外层函数this上
    }
  };
  console.log(obj5.f())
  console.log(obj6.f())
  //obj7.f()设置好了this
  console.log(obj7.f()())//function由对象创建 返回的是一个函数
  let ftest = obj7.f()
  console.log(ftest())
  let ftest1 = obj7.f
  // !!!
  // 但是注意，如果你只是引用obj的方法，
  console.log(ftest1()())
  console.groupEnd()
  // 而没有调用它
  console.group('作为对象的方法')
  let obj8 = {
    a: 'obj8',
    f: function () {
      return this.a;
    }
  };
  console.log(obj8.f())
  let obj9 = {
    a: 'obj9',
  };

  function ff() {
    return this.a;
  }

  let obj10 = {
    a: 'obj10',
  };
  obj9.ff = ff
  obj10.ff = obj9.ff // obj10 --->this 的绑定只受最靠近的成员引用的影响
  //谁调用函数更重要
  console.log(obj9.ff())
  console.log(obj10.ff())
  console.groupEnd()
  console.group('原型链中的 this')
  let obj11 = {
    f: function () {
      return this.a + this.b;
    }
  };
  let p = Object.create(obj11);
  p.a = 1;
  p.b = 4;

  console.log(p.f()); // 5 this指向调用者 即使属性在原型链上
  console.groupEnd()
  console.group('getter 与 setter 中的 this')
  let obj12 = {
    a: 1,
    b: 2,
    c: 3,
    get average() {
      return (this.a + this.b + this.c) / 3;
    }
  };
  console.log(obj12.average)
  console.groupEnd()
  console.group('作为构造函数')

  function C() {
    this.a = 37;
  }

  let obj13 = new C();
  console.log(obj13.a); // logs 37


  function C2() {
    this.a = 37;
    return {a: 38};
  }

  let obj14 = new C2();
  console.log(obj14.a); // logs 38 因为在调用构造函数的过程中，手动的设置了返回对象，与this绑定的默认对象被丢弃了
  console.groupEnd()
  console.group('作为一个DOM事件处理函数')
  console.log('')

  function judge(e) {
    console.log(this === e.currentTarget);
    console.log(this)
  }

  let btn = document.getElementById('btn')
  let btn1 = document.getElementsByTagName('button')
  btn.addEventListener('click', judge)
  btn1[0].addEventListener('click', judge)//addEventListener会覆盖上一个相同的监听事件
  console.groupEnd()

  console.group('箭头函数')

  function foo1() {
    setTimeout(() => {
      console.log('id:', this.id);
    }, 100);
  }

  var id = 21;

  foo1.call({ id: 42 });

  function Timer() {
    this.s1 = 0;
    this.s2 = 0;
    // 箭头函数->this绑定定义时所在的作用域->指向当前对象
    window.setInterval(() => this.s1++, 1000);
    // 普通函数->运行时所在的作用域（即全局对象）->运行环境
    window.setInterval(function () {
      this.s2++;
    }, 1000);
  }

  var timer = new Timer();

  setTimeout(() => console.log('s1: ', timer.s1), 3100);
  setTimeout(() => console.log('s2: ', timer.s2), 3100);

  // function foo() {
  //   setTimeout(() => {
  //     console.log('id:', this.id);
  //   }, 100);
  // }
  // function foo1() {
  //   () => {console.log('id:', this.id);}
  // }
  //
  // var id = 21;
  //
  // foo.call({id: 42});
  // foo1.call({id: 42});
  console.groupEnd()
</script>
</body>
</html>
